﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using SlimDX;
using SlimDX.D3DCompiler;
using SlimDX.DXGI;
using SlimDX.Direct3D11;

using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Vxl.Utils
{
    public class CustomEffect
    {
        public string name;

        private Effect effect = null;
        private InputLayout VertexLayout = null;
        private EffectTechnique Technique = null;

        private bool texturesSet = false;
        private List<String> textureFileNames = new List<String>();
        private Dictionary<string, EffectVariable> effectVariables = new Dictionary<string, EffectVariable>();
        private Dictionary<string, ShaderResourceView> textureSRV = new Dictionary<string, ShaderResourceView>();
        
        public CustomEffect(string shaderName, byte[] compiledShader, string shaderVersion, string technique, List<InputElement> layout)
        {
            Initialize(shaderName, compiledShader, shaderVersion, technique, layout);
        }

        public void Initialize(string shaderName, byte[] compiledShader, string shaderVersion, string technique, List<InputElement> layout ){
            this.name = shaderName;
            ShaderBytecode bytecode = null;

            if (compiledShader != null)
            {                
                DataStream ds = new DataStream(compiledShader.ToArray<byte>(), true, true);
                bytecode = new ShaderBytecode(ds);
            }
            else
            {
                bytecode = ShaderBytecode.CompileFromFile(shaderName, shaderVersion, ShaderFlags.None, EffectFlags.None);
            }

            if (bytecode == null)
            {
                // something went horribly wrong...
                MessageBox.Show("Could not create effect " + shaderName);
            }

            effect = new Effect(Globals.Device, bytecode);
            Technique = effect.GetTechniqueByName(technique);

            var pass = Technique.GetPassByIndex(0);

            VertexLayout= new InputLayout(Globals.Device, pass.Description.Signature, layout.ToArray<InputElement>());
        }

        public void PreDraw(){
		    Globals.Device.ImmediateContext.InputAssembler.InputLayout = VertexLayout;
	    }

        public void Draw(int primitiveCount, int vertexSize){ 
            // render
		    
		    for( int p = 0; p < Technique.Description.PassCount; ++p )
		    {
			    Technique.GetPassByIndex( p ).Apply( Globals.Device.ImmediateContext );
			    for(int i = 0; i<=primitiveCount; i+=vertexSize)
				    Globals.Device.ImmediateContext.Draw( vertexSize, i);
		    }
        }

        public void Draw(int primitiveCount){ 
            // render
		    
		    for( int p = 0; p < Technique.Description.PassCount; ++p )
		    {
			    Technique.GetPassByIndex( p ).Apply( Globals.Device.ImmediateContext );
			    
			    Globals.Device.ImmediateContext.Draw( primitiveCount, 0 );
		    }
        }

	    public void DrawIndexed(int indexCount){
		    // render
		    for( int p = 0; p < Technique.Description.PassCount; ++p )
		    {
			    Technique.GetPassByIndex( p ).Apply( Globals.Device.ImmediateContext );
			    Globals.Device.ImmediateContext.DrawIndexed( indexCount, 0, 0);
		    }
	    }

	    public void DrawAuto(){		
		    // render
		    for( int p = 0; p < Technique.Description.PassCount; ++p )
		    {
			    Technique.GetPassByIndex( p ).Apply( Globals.Device.ImmediateContext );
			    Globals.Device.ImmediateContext.DrawAuto();
		    }
	    }

	    public void AddVariable(string variable){
		    if(!effectVariables.ContainsKey(variable)){
			    effectVariables[variable] = effect.GetVariableByName(variable);
		    }
	    }

	    public void AddTexture( string variable, string texture ){
		    textureFileNames.Add(texture);
		    AddVariable(variable);
		    texturesSet = false;
	    }

	    public void SetTexture(string variable, string texture){
		    if(!texturesSet){
			    texturesSet = true;

			    //load textures
			    for ( int i=0; i < (int) textureFileNames.Count; i++ )
			    {
				    textureSRV[textureFileNames[i]] = ShaderResourceView.FromFile(Globals.Device, texture);
			    }
		    }
            SetTexture(variable, textureSRV[texture]);
	    }
        
        public void SetDepthStencil(string variable, DepthStencilState stencilState){
            effectVariables[variable].AsDepthStencil().SetDepthStencilState(0, stencilState);
        }

	    public void SetTexture(string variable, ShaderResourceView textureSRV){
            effectVariables[variable].AsResource().SetResource(textureSRV);
	    }

	    public void SetMatrix(string variable, Matrix matrix){
		    effectVariables[variable].AsMatrix().SetMatrix(matrix);                		
	    }

	    public void SetVector(string variable, Vector4 vector){
            effectVariables[variable].AsVector().Set(vector);
	    }

        public void SetVector(string variable, Vector3 vector){
		    effectVariables[variable].AsVector().Set(vector);
	    }

        public void SetVector(string variable, Vector2 vector){
		    effectVariables[variable].AsVector().Set(vector);
	    }

	    public void SetBool(string variable, bool scalar){
		    effectVariables[variable].AsScalar().Set(scalar);
	    }

	    public void SetInt(string variable, int scalar){
		    effectVariables[variable].AsScalar().Set(scalar);
	    }

	    public void SetFloat(string variable, float scalar){
		    effectVariables[variable].AsScalar().Set(scalar);
	    }

	    public void CleanUp(){

		    if(effect == null){
			    return;
		    }

		    if(VertexLayout != null){
			    VertexLayout.Dispose();
                VertexLayout = null;
		    }

            foreach(ShaderResourceView srv in textureSRV.Values){
                srv.Dispose();            
            }

		    textureSRV.Clear();

		    effectVariables.Clear();

		    textureFileNames.Clear();
            
		    if(effect != null){
			    effect.Dispose();
			    effect = null;
		    }
	    }

	    // creates the vertex buffer every time, since this geometry shader is considered a special worker for the cpu
	    /*ID3D10Buffer* CustomEffect::WriteToGeometryShader(D3D10_PRIMITIVE_TOPOLOGY inputTopology, ID3D10Buffer* inputBuffer, int inputVertexCount, ID3D10Buffer* outputBuffer){
		    CheckForGeometryShaderSupport();

		    // Set IA parameters
		    ID3D10Buffer* pBuffers[1];
		    pBuffers[0] = inputBuffer;

		    D3D10_BUFFER_DESC desc;
		    inputBuffer->GetDesc(&desc);

		    UINT stride[1] = { desc.ByteWidth / inputVertexCount };
		    UINT offset[1] = { 0 };

		    Globals::Device->IASetVertexBuffers( 0, 1, pBuffers, stride, offset );
		    Globals::Device->IASetPrimitiveTopology( inputTopology );

		    // Point to the correct output buffer
		    pBuffers[0] = outputBuffer;
		    Globals::Device->SOSetTargets( 1, pBuffers, offset );

		    PreDraw();
		    Draw(inputVertexCount); 

		    // Get back to normal
		    pBuffers[0] = NULL;
		    Globals::Device->SOSetTargets( 1, pBuffers, offset );

		    //std:swap(inputBuffer, outputBuffer);

		    return outputBuffer;
	    }*/

    }
}
